

#ifndef __HTTP_H__
#define __HTTP_H__

#include "knutil.h"
#include "URI.h"
#include "HTTP/Status.h"
#include "HTTP/impl.h"

#include <string>
#include <vector>
#include <map>
#include <algorithm>


namespace HTTP {

class HttpHeader
{
	typedef std::string string;// bad code
public:
	HttpHeader(){init_keys();}

	void clear()
	{
		headers.clear();
	}

	void add_header(const string& key, const string& value)
	{
		string tkey = add_key(key);
		headers[tkey] = value;
	}
	bool has_header(const string& key)  const
	{
		//Bad code
		//how to make this function const
		std::map<string, string>& _keys = const_cast<std::map<string, string>&>(keys);
		const std::map<string, string>& _headers = headers;
		string skey = knlib::util::stolower(key);
		if(_keys.find(skey) == _keys.end())
			return false;
		const string tkey =  _keys[skey];
		return _headers.find(tkey)!=_headers.end();
	}
	void test_add_header(const string& key, const string& value)
	{
		string tkey = add_key(key);
		if(headers.find(tkey)==headers.end())
			headers[tkey] = value;
	}
	void remove_header(const string& key)
	{
		string skey = knlib::util::stolower(key);
		std::map<string, string>::const_iterator i = keys.find(skey);
		if(i==keys.end())
			return;
		string tkey = keys[skey];
		headers.erase(tkey);
	}
	string get_header(const string& key)
	{
		//Bad code
		//how to make this function const
		std::map<string, string>& _headers = const_cast<std::map<string, string>&>(headers);
		return _headers[add_key(key)];
	}
	string to_string() const
	{
		string data;
		std::map<string, string>::const_iterator pos;
		//Bad code
		//too many temp objects
		for(pos=headers.begin(); pos!=headers.end(); pos++)
			data += (*pos).first + ": " + (*pos).second + "\r\n";
		return data;
	}
private:
	std::map<string, string> headers;
	std::map<string, string> keys;
	string add_key(const string& key)
	{
		string skey = knlib::util::stolower(key);
		if(keys.find(skey) == keys.end())
			keys[skey] = key;
		return keys[skey];
	}
	void init_keys()
	{
		keys["accept"] = "Accept";
		keys["accept-charset"] = "Accept-Charset";
		keys["accept-encoding"] = "Accept-Encoding";
		keys["accept-language"] = "Accept-Language";
		keys["allow"] = "Allow";
		keys["authorization"] = "Authorization";
		keys["cookie"] = "Cookie";
		keys["connection"] = "Connection";
		keys["content-encoding"] = "Content-Encoding";
		keys["content-length"] = "Content-Length";
		keys["content-type"] = "Content-Type";
		keys["date"] = "Date";
		keys["expires"] = "Expires";
		keys["from"] = "From";
		keys["host"] = "Host";
		keys["if-modified-since"] = "If-Modified-Since";
		keys["keep-alive"] = "Keep-Alive";
		keys["last-modified"] = "Last-Modified";
		keys["location"] = "Location";
		keys["mime-version"] = "MIME-Version";
		keys["pragma"] = "Pragma";
		keys["referer"] = "Referer";
		keys["server"] = "Server";
		keys["user-agent"] = "User-Agent";
		keys["www-authenticate"] = "WWW-Authenticate";
	}
};


class HttpForm
{
	typedef std::string string;// bad code
public:
	HttpForm(){}
	HttpForm(const std::string& k, const std::string& v)
	{
		add_data(k, v);
	}
	HttpForm(const std::string& k1, const std::string& v1,
	         const std::string& k2, const std::string& v2)
	{
		add_data(k1, v1);
		add_data(k2, v2);
	}
	HttpForm(const std::string& k1, const std::string& v1,
	         const std::string& k2, const std::string& v2,
	         const std::string& k3, const std::string& v3)
	{
		add_data(k1, v1);
		add_data(k2, v2);
		add_data(k3, v3);
	}
	HttpForm(const std::string& k1, const std::string& v1,
	         const std::string& k2, const std::string& v2,
	         const std::string& k3, const std::string& v3,
	         const std::string& k4, const std::string& v4)
	{
		add_data(k1, v1);
		add_data(k2, v2);
		add_data(k3, v3);
		add_data(k4, v4);
	}
	
//	HttpForm(const HttpForm&);
	bool empty() const {return data.empty();}
	void add_data(const string& key, const string& value)
	{
		data[key] = value;
	}
	string to_line() const
	{
		return URI::encode_data(data);
	}
	operator const std::map<string, string>&() const
	{
		return data;
	}
private:
	std::map<string, string> data;
};

class HttpRequestType
{
	typedef std::string string;// bad code
public:
	static const HttpRequestType HEAD;
	static const HttpRequestType GET;
	static const HttpRequestType POST;
	static const HttpRequestType GET_1_0;

	string get_method() const
	{
		return method;
	}
	
	string request_line(const string& file="/") const
	{
		string line(method);
		line += ' ';
		line += file;
		line += ' ';
		line += version;
		line += "\r\n";
		return line;
	}
	
	string request(const string& file, const HttpHeader& headers, const HttpForm& form) const
	{
		return request(file, headers, form.to_line());
	}
	string request(const string& file, const HttpHeader& headers, const string& post_data) const
	{
		string req = request_line(file);
		req += headers.to_string();
		if(method == "POST" && !headers.has_header("Content-Type"))
		{
			req += "Content-Type: ";
			req += "application/x-www-form-urlencoded";
			req += "\r\n";
		}
		if(with_body)
		{
//			string post_data = form.to_line();
			if( !headers.has_header("Content-Length") )
			{
				req += "Content-Length: ";
				req += knlib::util::itostring(post_data.length());
				req += "\r\n";
			}
			req += "\r\n";
			req += post_data;
		}
		else
		{
			req += "\r\n";
		}
		return req;
	}
	/*
	bool operator==(const HttpRequestType& rhs) const
	{
		return method==rhs.method && version==rhs.version;
	}
	*/
private:
	HttpRequestType(const string& m, bool b=false, const string& v="HTTP/1.1")
		:method(m), version(v), with_body(b) {}
	const string method;
	const string version;
	const bool with_body;
};


const HttpRequestType HttpRequestType::HEAD("HEAD");
const HttpRequestType HttpRequestType::GET("GET");
const HttpRequestType HttpRequestType::POST("POST", true);
const HttpRequestType HttpRequestType::GET_1_0("GET", false, "HTTP/1.0");

class RequestObject
{
	typedef std::string string;// bad code
public:
	RequestObject(const string& raw_url, const HttpRequestType& req_type=HttpRequestType::GET)
	:url_object(raw_url), type(req_type) {init();}
//	RequestObject(const URI::URL& url, const HttpRequestType& req_type=HttpRequestType::GET)
//	:url_object(url), type(req_type) {init();}
//	RequestObject(const RequestObject &);
	
	/*
	string head() const
	{
		return to_string(HttpRequestType::HEAD);
	}
	string get() const
	{
		return to_string(HttpRequestType::GET);
	}
	string post()  const
	{
		return to_string(HttpRequestType::POST);
	}
	*/
	string to_string(const HttpRequestType& req_type) const
	{
		if(content_data=="")
			return req_type.request(url_object.get_file(), header, form_data);
		else if(form_data.empty())
			return req_type.request(url_object.get_file(), header, content_data);
		else
			return req_type.request(url_object.get_file(), header, form_data.to_line()+content_data);
	}
	string to_string() const
	{
		return to_string(type);
	}
	string host() const
	{
		return url_object.get_host();
	}
	void change_url(const std::string& new_url)
	{
		url_object = URI::URL(new_url);
	}
	bool has_header(const string& key) const
	{
		return header.has_header(key);
	}
	void add_header(const string& key, const string& value)
	{
		header.add_header(key, value);
	}
	void test_add_header(const string& key, const string& value)
	{
		header.test_add_header(key, value);
	}
	void remove_header(const string& key)
	{
		header.remove_header(key);
	}
	void set_header(const HttpHeader& new_header)
	{
		header = new_header;
		header.test_add_header("Host", url_object.get_host());
	}
	void add_data(const string& key, const string& value)
	{
		form_data.add_data(key, value);
	}
	void set_form(const HttpForm& new_form)
	{
		form_data = new_form;
	}
	void content_type(const string& ctype)
	{
		add_header("Content-Type", ctype);
	}
	void content(const string& c)
	{
		content_data = c;
	}

private:
	URI::URL url_object;
	HttpHeader header;
	HttpForm form_data;
	string content_data;
	void init()
	{
		header.add_header("Host", url_object.get_host());
//		header.add_header("User-Agent", LWP_AGENT);
	}
	const HttpRequestType type;
};



class ResponseObject
{
	typedef std::string string;// bad code
public:
	ResponseObject(const std::string& host):closed(false), host_(host){response_data.reserve(8192);}
//	ResponseObject(const ResponseObject& resp);

	void clear()
	{
		closed = false;
		response_data.clear();
		header.clear();
		statusline.clear();
		error_string.clear();
	}

	void write(const char*buffer, size_t length)
	{
		copy(buffer, buffer+length, back_inserter(response_data));
	}

	void close() const
	{
		// Bad: should check more exceptional response
		if(closed)
			return;
		closed = true;
		const char * rn = "\r\n";
		const char * nn = "\r\n\r\n";
		const char * bnn = "\n\n";
		std::vector<char>::const_iterator i, j, k, end;

		if(!error_string.empty())
		{
			statusline = error_string;
			return;
		}		
		if(response_data.empty())
		{
			statusline = "No response";
			return;
		}
		
		end = std::search(response_data.begin(), response_data.end(), nn, nn+4);
		if(end==response_data.end())
			end = std::search(response_data.begin(), response_data.end(), bnn, bnn+2);

		if(end==response_data.end())
		{
			statusline = "Bad response";
			return;
		}
		
		i = std::find(response_data.begin(), end, '\n');
		if(i==response_data.end())
		{
			statusline = "Bad response";
			return;
		}
		
		j = i+1;
		if(i!=response_data.begin() && *(i-1)=='\r')
			i--;
		statusline = string(response_data.begin(), i);
		i = j;
		string line;
		while(1)
		{
			j = std::find(i, end, '\n');
			if(j==i)
				break;
			k = std::find(i, j, ':');
			if(k!=j)
			{
				string key(i, k);
				i = k+2;
				k = j;
				if(k!=response_data.begin() && *(k-1)=='\r')
					k--;
				string val(i, k);
				header.add_header(key, val);
			}

			if(j==end)
				break;

			i = j+1;
		}

	}

	void set_error(const string& e)
	{
		error_string = e;
	}

	string get_error() const
	{
		if(error_string!="")
			return error_string;
		else
			return status_line();
		
	}


	bool has_header(const string& key) const
	{
		if(!closed) close();
		return header.has_header(key);
	}
	
	string get_header(const string& key) const
	{
		if(!closed) close();
		return header.get_header(key);
	}

	string status_line() const
	{
		return statusline;
	}
	string get_data() const
	{
		return string(response_data.begin(), response_data.end());
	}
	//Note
	//Content-Length
	//Chunk-Size
	string get_html() const
	{
		const char* nn = "\r\n\r\n";
		const char* bnn = "\n\n";
		std::vector<char>::const_iterator i = std::search(response_data.begin(), response_data.end(), nn, nn+4);
		if(i==response_data.end())
		{
			i = std::search(response_data.begin(), response_data.end(), bnn, bnn+2);
			if(i==response_data.end())
				return "";
			else
				i += 2;
		}
		else
		{
			i += 4;
		}

		if(has_header("Content-Length"))
		{
			int length = atoi(get_header("Content-Length").c_str());
			if( response_data.end() - i < length )
				return string(i, response_data.end());
			else
				return string(i, i+length);
		}
		else if( has_header("Transfer-Encoding") && (get_header("Transfer-Encoding")=="chunked") )
		{
			return impl::read_chunk(i, response_data.end());
		}
		else
		{
			return string(i, response_data.end());
		}
		
	}
	string content() const
	{
		return get_html();
	}
	string content_type() const
	{
		return get_header("Content-Type");
	}
	string header_string() const
	{
		return header.to_string();
	}
	string status_code() const
	{
		string raw_line = status_line();
		string::size_type i, j;
		if( (i = raw_line.find_first_of(" \t\n\f\r")) == string::npos)
			return "";
		if( (i = raw_line.find_first_not_of(" \t\n\f\r", i)) == string::npos )
			return "";
		if( (j = raw_line.find_first_of(" \t\n\f\r", i)) == string::npos )
			return "";
		return string(raw_line, i, j-i);
	}
	bool is_success() const
	{
		if( error_string!="" )
			return false;
		return HTTP::Status::is_success(status_code());
	}
	std::string host() const
	{
		return host_;
	}
	
private:
	std::vector<char> response_data;
	mutable HttpHeader header;
	mutable string statusline;
	mutable bool closed;
	string error_string;
	std::string host_;
};


typedef RequestObject Request;
typedef ResponseObject Response;

}//end namespace HTTP


#endif

